#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <printk.h>
#include "fsm.h"

static const char *fsm_msg_str(ubase_t msg)
{
    switch (msg)
    {
    case fsm_msg_alarm: return "ALARM";
    case fsm_msg_data:  return "DATA";
    case fsm_msg_state_change: return "CHANGE";
    default: return "NODEF";
    }
}

static const fsm_state_t *get_state_op(fsm_t *fsm, u8 state)
{
    int i;

    for (i = 0; i < fsm->state_nr; i++)
    {
        if (fsm->states[i].state == state)
            return &fsm->states[i];
    }
    return NULL;
}

static int fsm_schedule(fsm_t *fsm, void *arg)
{
    int ret = STATE_CTR_WAIT;

redo:
    if (fsm->init)
    {
        fsm->init = 0;
        ret = fsm->op->op1(fsm, fsm->xbuffer, fsm->xbuffer_size);
    }
    else
    {
        ret = fsm->op->op2(fsm, arg, arg ? fsm->rxlen : 0);
    }

    if (ret >= 0)
    {
        if (ret > 0 && fsm->fsm_write)
            fsm->fsm_write(fsm, fsm->xbuffer, ret);

        u32 wait = fsm->wait > 0 ? fsm->wait : fsm->op->wait;
        fsm->wait = 0;
        if (wait == 0 || wait == FSM_WAIT_MAX)
            goto redo;
        soft_timer_start(&fsm->xtimer, MS_TO_TICKS(wait), 0);
    }
    else
    {
        u8 state;
        switch (ret)
        {
        case STATE_CTR_NEXT:
            soft_timer_stop(&fsm->xtimer);
            state = fsm->op->next_state;
            break;
        case STATE_CTR_RETRY:
            soft_timer_stop(&fsm->xtimer);
            state = fsm->op->state;
            if (fsm->op->maxtry > 0)
                if (++fsm->tries >= fsm->op->maxtry)
                    state = fsm->op->fail_state;
            break;
        case STATE_CTR_FAIL:
            soft_timer_stop(&fsm->xtimer);
            state = fsm->op->fail_state;
            break;
        case STATE_CTR_CHAGE:
            soft_timer_stop(&fsm->xtimer);
            state = fsm->next;
            break;
        default:
            return ret;
        }
        fsm_setstate(fsm, state);
    }
    return ret;
}

static void fsm_do_setstate(fsm_t *fsm, const fsm_state_t *op)
{
    fsm->init = 1;

    const fsm_state_t *ori = fsm->op;
    if (ori != op)
    {
        fsm->cur_state = op->state;
        fsm->tries     = 0;
        fsm->op        = op;
    }

    {
        u8 lvl = fsm->silent ? LOG_LEVEL_DBG : LOG_LEVEL_INF;

        log_simple(LOG_MODULE_APP, lvl, "[SM]: state %s -> %s, tries[%d].\n",
                   ori ? ori->name : "nil", op->name, fsm->tries);
    }
    fsm_schedule(fsm, NULL);
}
static void fsm_ccb_cb(ccb_t *ccb)
{
    ccb_begin(ccb);
    for (;;)
    {
        ccb_msg_wait(ccb);

        ccb_msg_t msg;
        fsm_t *fsm = container_of(ccb, fsm_t, ccb);
        int r = ccb_msg_pop(ccb, &msg);
        assert(r > 0);

        APP_PD("[SM]: ccb recv msg[%s].\n", fsm_msg_str(msg.cmd));
        if (msg.cmd == fsm_msg_alarm)
            fsm_schedule(fsm, NULL);
        else if (msg.cmd == fsm_msg_state_change)
            fsm_do_setstate(fsm, msg.arg);
        else if (msg.cmd == fsm_msg_data)
            fsm_schedule(fsm, msg.arg);
    }
    ccb_end(ccb);
}
int fsm_recv_data(fsm_t *fsm, const void *data, size_t len)
{
    APP_PD("[SM]: recved %zu bytes.\n", len);

    fsm->rxlen = len;

    ccb_msg_t msg = {
        .cmd = fsm_msg_data,
        .arg = (void *)data,
    };
    ccb_msg_push(&fsm->ccb, &msg);

    fsm_ccb_cb(&fsm->ccb);
    return 0;
}

static void fsm_timer_handle(void *data)
{
    fsm_t *fsm = data;

    APP_PD("[SM]: state[%s] timeout.\n", fsm->op->name);
    ccb_msg_t msg = {
        .cmd = fsm_msg_alarm,
    };
    ccb_msg_push(&fsm->ccb, &msg);
}

int fsm_getstate(fsm_t *fsm)
{
    return fsm->cur_state;
}

int fsm_setstate(fsm_t *fsm, u8 state)
{
    const fsm_state_t *op = get_state_op(fsm, state);
    if (!op)
    {
        APP_PE("[SM]: can not find state[%d].\n", state);
        return -1;
    }

    ccb_msg_t msg = {
        .cmd = fsm_msg_state_change,
        .arg = (void *)op,
    };
    ccb_msg_push(&fsm->ccb, &msg);
    return 0;
}
int fsm_attach(ev_loop_t *loop, fsm_t *fsm)
{
    fsm->cur_state = FSM_INVALID_STATE;
    soft_timer_add(loop, &fsm->xtimer, "fsm", fsm_timer_handle, fsm);
    ccb_add(loop, &fsm->ccb, fsm_ccb_cb, fsm);
    return 0;
}
void fsm_detach(fsm_t *fsm)
{
    soft_timer_del(&fsm->xtimer);
    ccb_del(&fsm->ccb);
}

